#include "message.h"
#include "fingerprint.h"
#include "log.h"
#include <map>

using std::map;
using std::make_pair;
using messages::ByteArrayAppender;
using messages::ByteArrayParser;

ClientId Message::default_source_ = 0xdeadbeef;

map<int, MessageRegister::InstanceFunction>& god() {
    static map<int, MessageRegister::InstanceFunction> messages_types;
    return messages_types;
}

MessageRegister::MessageRegister(int type, InstanceFunction function) {
    LOG(INFO) << "registering message type " << type;

    ASSERT(god().find(type) == god().end());
    god().insert(make_pair(type, function));
}

Message* MessageRegister::instantiate(int type) {
    map<int, InstanceFunction>::iterator it = god().find(type);
    if (it == god().end()) {
        LOG(WARN) << "Unknown message type " << type;
        return new Message(type);
    }
    return it->second();
}

void Message::parse_array(const QByteArray* array) {
    int position = 0;

    parse_array(array, &position);

    // verificam ca am parsat tot mesajul
    CHECK_EQ(position, array->size());

    // verificam ca avem lungimea corecta
    CHECK_EQ(length_, array->size());
}

const QByteArray* Message::build_array() const {
    QByteArray* array = new QByteArray;

    build_array(array);

    // seteaza lungimea mesajului
    quint16 length = quint16(array->size());
    PRINT(array->size());

    // suprascriem inceputul mesajului cu lungimea si hashul corect
    QByteArray temp;
    ByteArrayAppender a(&temp);
    a.append(length);
    PRINT(temp.size());

    for (int i = 0; i < temp.size(); ++i) {
        PRINT((int)temp.at(i));
        array->data()[i] = temp.at(i);
    }

    for (int i = 0; i < array->size(); ++i)
        LOG(INFO) << "[" << i << "] = " << array->at(i) << " " << (int)(array->at(i));

    return array;
}

void Message::parse_array(const QByteArray* array, int* position) {
    ByteArrayParser p(array, position);

    p.parse(&length_);
    p.parse(&type_);
    p.parse(&source_);
}

void Message::build_array(QByteArray* array) const {
    ByteArrayAppender a(array);

    a.append(quint16(0));  // lasam spatiu pentru lungime
    a.append(type_);
    a.append(source_);
}

namespace messages {

// class ByteArrayAppender
void ByteArrayAppender::append(quint8 value) {
    for (int i = 0; i < (int)sizeof(quint8); ++i)
        array_->append(quint8((value >> i*8) & 0xff));
}

void ByteArrayAppender::append(quint16 value) {
    for (int i = 0; i < (int)sizeof(quint16); ++i)
        array_->append(quint8((value >> i*8) & 0xff));
}

void ByteArrayAppender::append(quint32 value) {
    for (int i = 0; i < (int)sizeof(quint32); ++i)
        array_->append(quint8((value >> i*8) & 0xff));
}

void ByteArrayAppender::append(quint64 value) {
    for (int i = 0; i < (int)sizeof(quint64); ++i)
        array_->append(quint8((value >> i*8) & 0xff));
}

void ByteArrayAppender::append(const FingerPrint& value) {
    for (int i = 0; i < value.signature_size(); ++i)
        array_->append(value.signature(i));
}

void ByteArrayAppender::append(const QByteArray& value) {
    ASSERT(0 <= value.size() && value.size() < 255);
    array_->append(quint8(value.size()));
    array_->append(value);
}

// class ByteArrayParser
void ByteArrayParser::parse(quint8* value) {
    CHECK_LE(*position_ + (int)sizeof(quint8), array_->size());
    *value = 0;
    for (int i = 0; i < (int)sizeof(quint8); ++i)
        *value += quint8(quint8(array_->at(i+*position_))) << i*8;
    *position_ += sizeof(quint8);
}

void ByteArrayParser::parse(quint16* value) {
    CHECK_LE(*position_ + (int)sizeof(quint16), array_->size());
    *value = 0;
    for (int i = 0; i < (int)sizeof(quint16); ++i)
        *value += quint16(quint8(array_->at(i+*position_))) << i*8;
    *position_ += sizeof(quint16);
}

void ByteArrayParser::parse(quint32* value) {
    CHECK_LE(*position_ + (int)sizeof(quint32), array_->size());
    *value = 0;
    for (int i = 0; i < (int)sizeof(quint32); ++i)
        *value += quint32(quint8(array_->at(i+*position_))) << i*8;
    *position_ += sizeof(quint32);
}

void ByteArrayParser::parse(quint64* value) {
    CHECK_LE(*position_ + (int)sizeof(quint64), array_->size());
    *value = 0;
    for (int i = 0; i < (int)sizeof(quint64); ++i)
        *value += quint64(quint8(array_->at(i+*position_))) << i*8;
    *position_ += sizeof(quint64);
}

void ByteArrayParser::parse(FingerPrint* value) {
    CHECK_LE(*position_ + value->signature_size(), array_->size());
    for (int i = 0; i < value->signature_size(); ++i)
        value->set_signature(i, array_->at(i+*position_));
    *position_ += value->signature_size();
}

void ByteArrayParser::parse(QByteArray* value) {
    CHECK_LE(*position_ + 1, array_->size());

    value->clear();
    int size = quint8(array_->at(*position_));
    ++*position_;

    CHECK_LE(*position_ + size, array_->size());
    *value = array_->mid(*position_, size);
    *position_ += size;
}

Message* bytearray_to_message(QByteArray* array) {
    // TODO(alexandru): this is a very weird hack
    // daca e schimbat headerul atunci se schimba si aceasta functie
    CHECK_LE(11, array->size());
    Message* m = MessageRegister::instantiate(array->at(2));
    m->parse_array(array);
    return m;
}

}

